package com.alex.megacj.handler;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.StatusLine;
import org.springframework.beans.factory.annotation.Required;

import com.alex.megacj.StatsHandler;
import com.alex.megacj.affiliate.AffiliateLinkRewriteException;
import com.alex.megacj.affiliate.AffiliateLinkRewriter;
import com.alex.megacj.alias.AliasManager;
import com.alex.megacj.filter.ContentFilter;
import com.alex.megacj.filter.RequestFilter;
import com.alex.megacj.logger.RequestLogger;
import com.alex.megacj.logger.RequestType;
import com.alex.megacj.scenario.ScenarioManager;
import com.alex.megacj.utils.PageUtils;
import com.alex.utils.http.HttpResult;
import com.alex.utils.http.RequestBuilder;
import com.alex.utils.http.RequestBuilderFactory;

public class ProxyHandler {

	private AliasManager aliasManager;

	private StatsHandler statsHandler;

	private RequestBuilderFactory requestBuilderFactory;

	private AffiliateLinkRewriter affiliateLinkRewriter;

	private RequestLogger requestLogger;

	private ScenarioManager scenarioManager;

	private Set<String> ignoreHeaders;

	public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
		RequestContext context = new RequestContext(request, response);
		parseHostAndScenario(context);

		for (RequestFilter filter : context.scenario.getRequestFilters()) {
			if (filter.matches(context)) {
				if (filter.filterRequest(context)) {
					return;
				}
			}
		}

		if (!context.scenario.getUrlMapper().mapRequest(context)) {
			response.sendError(404);
			return;
		}

		if ("/stats".equals(context.targetUrlParsed.getPath())) {
			statsHandler.handleStats(context);
			return;
		}

		processUserSource(context);

		String simplifiedURL = context.scenario.simpifyURL(context);
		if (simplifiedURL != null) {
			response.sendRedirect(context.scenario.getUrlMapper().replaceLink(simplifiedURL, context));
			return;
		}

		HttpResult httpResult = sendTargetRequest(context);

		try {
			boolean isHTML = ProxyHandlerUtils.isHTML(httpResult);

			if (isHTML && !httpResult.getResponse().containsHeader("location")) {
				if (!context.scenario.isAllowedHost(context.targetHost)) {
					try {
						context.requestType = RequestType.SELL;
						requestLogger.log(context);
						response.sendRedirect(affiliateLinkRewriter.rewriteURL(context.targetHost, context.targetUrl));
					} catch (AffiliateLinkRewriteException e) {
						response.sendRedirect(context.scenario.getSellURL());
					}
					return;
				} else {
					if (context.referer != null) {
						if (isScenarioURL(context.referer, context)) {
							context.requestType = RequestType.CLICK;
						} else {
							context.requestType = RequestType.IN;
						}
					} else {
						context.requestType = RequestType.NOREF;
					}
				}
			}

			forwardHeaders(httpResult, context);
			forwardContent(httpResult, isHTML, context);

			if (!context.dontLog && isHTML) {
				requestLogger.log(context);
			}
		} finally {
			httpResult.close();
		}
	}

	private void parseHostAndScenario(RequestContext context) {
		String host = context.request.getServerName();
		context.scenario = scenarioManager.getScenario(host, false);
		if (context.scenario != null) {
			context.mirrorHost = host;
			return;
		}

		context.aliasHost = aliasManager.resolveAlias(host);
		if (context.aliasHost != null) {
			host = context.aliasHost;
		}
		int p = host.lastIndexOf(".");
		if (p < 0) {
			throw new IllegalArgumentException("Could not parse host: " + host);
		}
		p = host.lastIndexOf(".", p - 1);
		if (p < 0) {
			throw new IllegalArgumentException("Could not parse host: " + host);
		}
		context.targetHost = host.substring(0, p);
		context.mirrorHost = host.substring(p + 1);

		context.scenario = scenarioManager.getScenario(context.mirrorHost, true);
	}

	private boolean isScenarioURL(String referer, RequestContext context) {
		try {
			String host = new URL(referer).getHost();
			if (host.endsWith("." + context.mirrorHost)) {
				host = host.substring(0, host.length() - context.mirrorHost.length() - 1);
			} else {
				return false;
			}
			return context.scenario.isAllowedHost(host);
		} catch (MalformedURLException e) {
			return false;
		}
	}

	private void processUserSource(RequestContext context) {
		context.referer = context.request.getHeader("referer");
		context.source = (String) context.request.getSession().getAttribute("USER_SOURCE");
		if (context.source == null && context.referer != null) {
			context.source = context.referer;
			context.request.getSession().setAttribute("USER_SOURCE", context.source);
		}
	}

	private HttpResult sendTargetRequest(RequestContext context) throws IOException {
		RequestBuilder requestBuilder = requestBuilderFactory.get(context.targetUrl);
		Enumeration<String> headerNames = context.request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String name = headerNames.nextElement();
			if (ignoreHeaders.contains(name) || name.startsWith("x-")) {
				continue;
			}
			Enumeration<String> headers = context.request.getHeaders(name);
			while (headers.hasMoreElements()) {
				String value = headers.nextElement();
				if ("referer".equals(name)) {
					try {
						String mappedUrl = context.scenario.getUrlMapper().mapURL(value);
						if (mappedUrl != null) {
							value = mappedUrl;
						}
					} catch (MalformedURLException e) {
					}
				}
				value = StringUtils.remove(value, "." + context.mirrorHost);
				requestBuilder.header(ProxyHandlerUtils.formatHeader(name), value);
			}
		}
		return requestBuilder.getResult();
	}

	@SuppressWarnings("deprecation")
	private void forwardHeaders(HttpResult httpResult, RequestContext context) {
		Set<String> setHeaders = new HashSet<String>();
		for (Header header : httpResult.getResponse().getAllHeaders()) {
			String name = header.getName().toLowerCase();
			if (ignoreHeaders.contains(name)) {
				continue;
			}

			String value = header.getValue();
			if ("location".equals(name)) {
				value = context.scenario.getUrlMapper().replaceLink(value, context);
				context.dontLog = true;
			} else {
				value = StringUtils.replace(value, context.targetUrlParsed.getHost(), context.targetUrlParsed.getHost() + "."
						+ context.mirrorHost);
			}

			if (setHeaders.add(header.getName())) {
				context.response.setHeader(header.getName(), value);
			} else {
				context.response.addHeader(header.getName(), value);
			}
		}
		StatusLine statusLine = httpResult.getResponse().getStatusLine();
		context.response.setStatus(statusLine.getStatusCode(), statusLine.getReasonPhrase());
	}

	private void forwardContent(HttpResult httpResult, boolean html, RequestContext context) throws IOException {
		if (html) {
			byte[] pageBytes = httpResult.getResponseBodyAsByteArray();
			String charset = PageUtils.getCharset(httpResult, pageBytes);
			String page = new String(pageBytes, charset);
			page = PageUtils.replaceLinks(page, context);
			page = PageUtils.processImages(page, context);
			page = PageUtils.processStyleLinks(page, context);
			page = PageUtils.deleteScripts(page);

			for (ContentFilter filter : context.scenario.getContentFilters()) {
				if (filter.matches(context)) {
					page = filter.filterContent(page, context);
				}
			}

			context.response.getOutputStream().write(page.getBytes(charset));
		} else if (isCSS(context)) {
			byte[] pageBytes = httpResult.getResponseBodyAsByteArray();
			String charset = PageUtils.getCharset(httpResult, pageBytes);
			String page = new String(pageBytes, charset);
			page = PageUtils.processCSS(page, context);
			context.response.getOutputStream().write(page.getBytes(charset));
		} else {
			context.response.getOutputStream().write(httpResult.getResponseBodyAsByteArray());
		}
	}

	private boolean isCSS(RequestContext context) {
		return context.targetUrl.endsWith(".css");
	}

	@Required
	public void setRequestBuilderFactory(RequestBuilderFactory requestBuilderFactory) {
		this.requestBuilderFactory = requestBuilderFactory;
	}

	@Required
	public void setIgnoreHeaders(Set<String> ignoreHeaders) {
		this.ignoreHeaders = ignoreHeaders;
	}

	@Required
	public void setRequestLogger(RequestLogger requestLogger) {
		this.requestLogger = requestLogger;
	}

	@Required
	public void setScenarioManager(ScenarioManager scenarioManager) {
		this.scenarioManager = scenarioManager;
	}

	@Required
	public void setAffiliateLinkRewriter(AffiliateLinkRewriter affiliateLinkRewriter) {
		this.affiliateLinkRewriter = affiliateLinkRewriter;
	}

	@Required
	public void setStatsHandler(StatsHandler statsHandler) {
		this.statsHandler = statsHandler;
	}

	@Required
	public void setAliasManager(AliasManager aliasManager) {
		this.aliasManager = aliasManager;
	}
}
